package com.hxzy.token;

import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.StrUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;
import com.hxzy.common.constans.RedisConstans;
import com.hxzy.common.enums.AckCode;
import com.hxzy.common.util.ServletUtils;
import com.hxzy.common.vo.R;
import com.hxzy.exception.ServiceException;
import com.hxzy.vo.AdminLoginVO;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 专业用来制作令牌
 */
@Component
@Log4j2
public class TokenService {

    /**
     * jwt的密码
     */
    @Value(value = "${token.security}")
    private String jwtSecurity;

    /**
     * redis过期时间（分钟）
     */
    @Value(value = "${token.expired}")
    private Long expired;

    /**
     * 请求头的变量名
     */
    @Value(value = "${token.header}")
    private String headerName;

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 生成令牌
     * @param adminLoginVO
     * @return
     */
    public String createJwtToken(AdminLoginVO adminLoginVO){
        //得到uuid
        String uuid= UUID.fastUUID().toString();
        //为自定义对象赋值
        adminLoginVO.setUuid(uuid);
        //更新redis中的值
        this.refreshRedis(adminLoginVO);
        //uuid使用jwt加密
        String jwtToken = this.generatorJwtToken(uuid);
        return jwtToken;
    }


    /**
     * 更新redis中的值
     * @param adminLoginVO
     */
    public void refreshRedis(AdminLoginVO adminLoginVO){
        //设定开始时间
        adminLoginVO.setCreateTime(System.currentTimeMillis());
        //设定结束时间
        adminLoginVO.setEndTime(adminLoginVO.getCreateTime()+ expired*1000L*60);
        //写入到redis中
        String redisKey= RedisConstans.getRedisKey(RedisConstans.TOKEN_KEY, adminLoginVO.getUuid());
        this.redisTemplate.opsForValue().set(redisKey, adminLoginVO, this.expired, TimeUnit.MINUTES);
    }

    /**
     * 把uuid再次使用jwt来进行加密
     * @param uuid
     * @return
     */
    private String generatorJwtToken(String uuid){
        Map<String,String> hashMap=new HashMap<>();
        hashMap.put(RedisConstans.TOKEN_USER_KEY, uuid);
        //使用jwt加密
        String jwt = JWT.create()
                .withClaim(RedisConstans.TOKEN_USER_KEY, hashMap)
                .sign(Algorithm.HMAC256(this.jwtSecurity));
        return jwt;
    }

    /**
     * 验证jwt须效性
     * @param token
     * @return
     */
    public boolean validateJwt(String token){
        try {
            Algorithm algorithm = Algorithm.HMAC256(this.jwtSecurity);
            JWTVerifier verifier = JWT.require(algorithm).build();
            DecodedJWT jwt = verifier.verify(token);
        }catch (Exception e){
            log.error(e.getMessage());
            return false;
        }
        return true;
    }

    /**
     * 解析jwt令牌
     * @param token
     * @return uuid
     */
    public String parseJwt(String token){
        String uuid="";
        try {
            DecodedJWT decode = JWT.decode(token);
            Claim claim = decode.getClaim(RedisConstans.TOKEN_USER_KEY);
            Map<String, Object> stringObjectMap = claim.asMap();
            uuid = stringObjectMap.get(RedisConstans.TOKEN_USER_KEY).toString();
        }catch(JWTDecodeException e){
            log.error(e.getMessage());
            throw new ServiceException( AckCode.APPLE_ILLEGAL);
        }
        return uuid;
    }

    /**
     * 根据请求header得到令牌，来获取当前用户是谁
     * @param request
     * @return
     */
    public AdminLoginVO getAdminLoginUser(HttpServletRequest request, HttpServletResponse response) {
        String jwtToken=request.getHeader(this.headerName);
        // 没有令牌
        if(StrUtil.isBlank(jwtToken)){
            //ServletUtils.renderString(response,  R.build(AckCode.SYSTEM_TOKEN_FAIL));
            return null;
        }

        //有令牌，判断是否 有  持票人标识 Bearer 空格 令牌
        if(jwtToken.startsWith("Bearer")){
            jwtToken= jwtToken.replace("Bearer","").trim();
        }

        //判断令牌有效性 jwt解码
        boolean success = this.validateJwt(jwtToken);
        if(!success){
           // ServletUtils.renderString(response,R.build(AckCode.APPLE_ILLEGAL));
            return null;
        }

        //得到jwt中的值
        String uuid=this.parseJwt(jwtToken);

        // 使用redis来查询
        String redisKey= RedisConstans.getRedisKey(RedisConstans.TOKEN_KEY,uuid);
        Object redisValue=this.redisTemplate.opsForValue().get(redisKey);
        if(redisValue==null){
            //ServletUtils.renderString(response,R.build(AckCode.APPLE_EXPIRED));
            return null;
        }

        // 把这个信息转换为用户信息
        AdminLoginVO adminLoginVO= (AdminLoginVO) redisValue;

        //判断令牌是否快过期了，如果快过期了，可以给它加点时间(还剩下足10分钟了)
        if(adminLoginVO.getEndTime() - System.currentTimeMillis()<= 1000L*60*10){
            this.refreshRedis(adminLoginVO);
        }

        return adminLoginVO;
    }



}
